package com.fulihui.wgj.service.event;

import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSON;
import com.fulihui.common.builder.rpc.RpcResultBuilder;
import com.fulihui.common.constants.OpenMpConstants;
import com.fulihui.common.conv.BeanConv;
import com.fulihui.common.enums.PlatformStatusEnum;
import com.fulihui.common.enums.WxOptionEnum;
import com.fulihui.common.enums.WxSystemConfigKeyEnum;
import com.fulihui.common.error.Errors;
import com.fulihui.common.result.rpc.BaseResult;
import com.fulihui.common.util.ConditionValidator;
import com.fulihui.common.util.PlatformUtil;
import com.fulihui.weixinsdk.WeixinSDK;
import com.fulihui.weixinsdk.encrypt_decode.com.qq.weixin.mp.aes.AesException;
import com.fulihui.weixinsdk.encrypt_decode.com.qq.weixin.mp.aes.WXBizMsgCrypt;
import com.fulihui.weixinsdk.model.AuthorizationInfo;
import com.fulihui.weixinsdk.model.AuthorizerInfo;
import com.fulihui.weixinsdk.request.ApiGetAuthorizerInfoRequest;
import com.fulihui.weixinsdk.request.ApiGetAuthorizerOptionRequest;
import com.fulihui.weixinsdk.request.ApiQueryAuthRequest;
import com.fulihui.weixinsdk.request.PreAuthCodeRequest;
import com.fulihui.weixinsdk.result.ApiGetAuthorizerInfoResult;
import com.fulihui.weixinsdk.result.ApiGetAuthorizerOptionResult;
import com.fulihui.weixinsdk.result.ApiQueryAuthResult;
import com.fulihui.weixinsdk.result.PreAuthCodeResult;
import com.fulihui.weixinsdk.util.XMLUtil;
import com.fulihui.wgj.biz.SyncBiz;
import com.fulihui.wgj.config.properties.AppProps;
import com.fulihui.wgj.dal.mybatis.domain.WxPlatform;
import com.fulihui.wgj.dal.mybatis.repository.WxPlatformRepository;
import com.fulihui.wgj.facade.dto.FuncInfoDTO;
import com.fulihui.wgj.facade.dto.WxSystemConfigDTO;
import com.fulihui.wgj.facade.msg.EventMsg;
import com.fulihui.wgj.facade.request.event.AuthCallBackRequest;
import com.fulihui.wgj.facade.request.event.AuthNotifyRequest;
import com.fulihui.wgj.facade.request.event.EventNotifyRequest;
import com.fulihui.wgj.facade.request.event.GetPreAuthUrlRequest;
import com.fulihui.wgj.facade.result.event.GetPreAuthUrlResult;
import com.fulihui.wgj.facade.result.system.WxSystemConfigResult;
import com.fulihui.wgj.facade.service.event.WxEventNotifyService;
import com.fulihui.wgj.facade.service.system.WxSystemCongfigService;
import com.fulihui.wgj.mq.kafka.producer.KafkaProducer;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

/**
 * @author wang_wx
 * @date 2018-06-20
 */
@Component
@Service(interfaceClass = WxEventNotifyService.class, version = "1.0.0", timeout = 3000)
public class WxEventNotifyServiceImpl implements WxEventNotifyService {

    private static final Logger LOGGER = LoggerFactory.getLogger(WxEventNotifyServiceImpl.class);

    @Autowired
    AppProps appProps;

    @Autowired
    WeixinSDK weixinSDK;

    @Autowired
    ThreadPoolTaskExecutor threadPool;

    @Resource
    WxSystemCongfigService wxSystemCongfigService;

    @Resource
    WxPlatformRepository wxPlatformRepository;

    @Autowired
    KafkaProducer kafkaProducer;

    @Autowired
    PlatformUtil platformUtil;

    @Autowired
    SyncBiz syncBiz;

    //TODO 需要改进
//    static final String[] FUNC_INFO_ZH = new String[26];

    @Override
    public BaseResult authNotify(AuthNotifyRequest request) {
        try {
            ConditionValidator.validate(request);
            WXBizMsgCrypt crypto = new WXBizMsgCrypt(appProps.getToken(),
                    appProps.getEncodingAesKey(), appProps.getAppId());

            String xml = crypto.decryptMsg(request.getMsg_signature(), request.getTimestamp(),
                    request.getNonce(), request.getPostDate());

            EventMsg message = XMLUtil.parseObject(xml, EventMsg.class);
            LOGGER.info("AuthNotify->" + ReflectionToStringBuilder.toString(message));
            // 扔消息队列异步处理
            kafkaProducer.sendPreAuthEvent(message);
        } catch (AesException e) {
            LOGGER.error(e.getMessage(), e);
        }
        return RpcResultBuilder.succ();
    }

    @Override
    public BaseResult eventNotify(EventNotifyRequest request) {
        try {
            ConditionValidator.validate(request);
            WXBizMsgCrypt crypto = new WXBizMsgCrypt(appProps.getToken(),
                    appProps.getEncodingAesKey(), appProps.getAppId());

            String xml = crypto.decryptMsg(request.getMsg_signature(), request.getTimestamp(),
                    request.getNonce(), request.getPostDate());

            EventMsg message = XMLUtil.parseObject(xml, EventMsg.class);
            message.setAppId(request.getAppId());
            LOGGER.info("EventNotify->" + ReflectionToStringBuilder.toString(message));
            //扔消息队列异步处理
            kafkaProducer.sendEventNotify(message);

        } catch (AesException e) {
            LOGGER.error(e.getMessage(), e);
        }
        return RpcResultBuilder.succ();
    }

    @Override
    public GetPreAuthUrlResult getPreAuthUrl(GetPreAuthUrlRequest request) {
        Date current = new Date(System.currentTimeMillis());

        WxSystemConfigResult result = wxSystemCongfigService.findConfigByCode(WxSystemConfigKeyEnum.component_access_token.getCode());
        WxSystemConfigDTO tokenInfo = result.getValue();

        if (tokenExpire(current, tokenInfo.getGmtModified())) {
            return RpcResultBuilder.fail(GetPreAuthUrlResult.class, Errors.WxSystemConfigError.COMPONENT_ACCESS_TOKEN_EXPIRED);
        }

        PreAuthCodeRequest preAuthCodeRequest = new PreAuthCodeRequest();
        preAuthCodeRequest.setComponent_appid(appProps.getAppId());
        preAuthCodeRequest.setComponent_access_token(tokenInfo.getDescript());

        PreAuthCodeResult authCodeResult = weixinSDK.invokeService(preAuthCodeRequest);
        GetPreAuthUrlResult succ = RpcResultBuilder.succ(GetPreAuthUrlResult.class);
        try {
            String preAuthUrl = String.format(OpenMpConstants.PRE_AUTH_URL_PREFIX, appProps.getAppId(),
                    authCodeResult.getPre_auth_code(), URLEncoder.encode(appProps.getRedirectUrl(), StandardCharsets.UTF_8.name()));
            succ.setAuthUrl(preAuthUrl);
        } catch (Exception e) {
            LOGGER.error("preAuthUrl format error{}", e);
        }
        LOGGER.info("user:{},pre auth getPreAuthUrl success!", request.getLoginId());
        return succ;
    }

    @Override
    public BaseResult authCallBack(AuthCallBackRequest request) {
        ConditionValidator.validate(request);
        LOGGER.info("authCallBack: start! authCode={}", request.getAuthCode());

        try {
            Date current = new Date(System.currentTimeMillis());
            WxSystemConfigResult result = wxSystemCongfigService.findConfigByCode(WxSystemConfigKeyEnum.component_access_token.getCode());
            WxSystemConfigDTO tokenInfo = result.getValue();

            if (tokenExpire(current, tokenInfo.getGmtModified())) {
                return RpcResultBuilder.fail(GetPreAuthUrlResult.class, Errors.WxSystemConfigError.COMPONENT_ACCESS_TOKEN_EXPIRED);
            }
            LOGGER.info("authCallBack: get token_info! token_info={}", tokenInfo);

            ApiQueryAuthRequest queryAuthRequest = new ApiQueryAuthRequest();
            queryAuthRequest.setComponent_appid(appProps.getAppId());
            queryAuthRequest.setComponent_access_token(tokenInfo.getDescript());
            queryAuthRequest.setAuthorization_code(request.getAuthCode());

            ApiQueryAuthResult queryAuthResult = weixinSDK.invokeService(queryAuthRequest);
            AuthorizationInfo info = queryAuthResult.getAuthorization_info();

            LOGGER.info("authCallBack: query Auth ! queryAuthResult={}", queryAuthResult);
            WxPlatform platform = wxPlatformRepository.queryOneByAppId(info.getAuthorizer_appid());
            //标识是 新添加
            boolean isNew = (null == platform);
            if (isNew) {
                platform = new WxPlatform();
                platform.setAppId(info.getAuthorizer_appid());
                platform.setGmtModified(current);
            }
            platform.setStatus(PlatformStatusEnum.sync.getCode());

            platform.setAuthorizerAccessToken(info.getAuthorizer_access_token());
            platform.setAuthorizerRefreshToken(info.getAuthorizer_refresh_token());
            platform.setTokenRefreshTime(current);

            List<FuncInfoDTO> funcInfoList = new LinkedList<>();
            info.getFunc_info().forEach(f -> {
                FuncInfoDTO funcInfoDTO = new FuncInfoDTO();
                funcInfoDTO.setId(f.getFuncscope_category().getId());
//                if (funcInfoDTO.getId() <= FUNC_INFO_ZH.length) {
//                    funcInfoDTO.setName(FUNC_INFO_ZH[funcInfoDTO.getId() - 1]);
//                } else {
//                    funcInfoDTO.setName("未知权限id:" + funcInfoDTO.getId());
//                }
                funcInfoList.add(funcInfoDTO);
            });
            platform.setFuncInfo(JSON.toJSONString(funcInfoList));
            ApiGetAuthorizerInfoRequest getAuthorizerInfoRequest = new ApiGetAuthorizerInfoRequest();
            getAuthorizerInfoRequest.setComponent_appid(appProps.getAppId());
            getAuthorizerInfoRequest.setComponent_access_token(tokenInfo.getDescript());
            getAuthorizerInfoRequest.setAuthorizer_appid(info.getAuthorizer_appid());
            ApiGetAuthorizerInfoResult getAuthorizerInfoResult = weixinSDK
                    .invokeService(getAuthorizerInfoRequest);
            AuthorizerInfo authorizerInfo = getAuthorizerInfoResult.getAuthorizer_info();

            platform.setNickName(authorizerInfo.getNick_name());
            platform.setHeadImg(authorizerInfo.getHead_img());
            platform.setServiceTypeInfo(authorizerInfo.getService_type_info().getId() + "");
            platform.setVerifyTypeInfo(authorizerInfo.getVerify_type_info().getId() + "");
            platform.setUserName(authorizerInfo.getUser_name());
            platform.setPrincipalName(authorizerInfo.getPrincipal_name());
            platform.setAlias(authorizerInfo.getAlias());
            platform.setBusinessInfo(JSON.toJSONString(authorizerInfo.getBusiness_info()));
            platform.setQrcodeUrl(authorizerInfo.getQrcode_url());

            // 刷新缓存
            platformUtil.set(platform.getAppId(), platform.getAuthorizerAccessToken());

            try {
                ApiGetAuthorizerOptionRequest getAuthorizerOptionRequest = new ApiGetAuthorizerOptionRequest();
                getAuthorizerOptionRequest.setComponent_appid(appProps.getAppId());
                getAuthorizerOptionRequest.setComponent_access_token(tokenInfo.getDescript());
                getAuthorizerOptionRequest.setAuthorizer_appid(info.getAuthorizer_appid());

                getAuthorizerOptionRequest.setOption_name(WxOptionEnum.location_report.getCode());
                ApiGetAuthorizerOptionResult optionResult = weixinSDK
                        .invokeService(getAuthorizerOptionRequest);
                platform.setLocationReport(optionResult.getOption_value());

                getAuthorizerOptionRequest.setOption_name(WxOptionEnum.voice_recognize.getCode());
                optionResult = weixinSDK.invokeService(getAuthorizerOptionRequest);
                platform.setVoiceRecognize(optionResult.getOption_value());

                getAuthorizerOptionRequest.setOption_name(WxOptionEnum.customer_service.getCode());
                optionResult = weixinSDK.invokeService(getAuthorizerOptionRequest);
                platform.setCustomerService(optionResult.getOption_value());
            } catch (Exception e) {
                LOGGER.warn("获取公众号选项信息(地理位置上报、语音识别开关、多客服开关)异常{}，不影响使用！", e.getMessage(), e);
            }

            final String appId = platform.getAppId();
            LOGGER.info("authCallBack: platform appId={}", appId);
            platform.setGmtModified(new Date());
            platform.setNextOpenid(null);

            if (isNew) {
                wxPlatformRepository.addWxPlatform(platform);
            } else {
                wxPlatformRepository.updateWxPlatform(platform);
            }
            LOGGER.info("authCallBack: platform appId = {} end", appId);

            // 异步任务拉取用户信息
            threadPool.execute(() -> syncBiz.syncUserFirst(appId));
            // 异步任务拉取标签信息
            threadPool.execute(() -> syncBiz.syncTag(appId));
            //异步任务拉取用户组信息
            threadPool.execute(() -> syncBiz.syncGroup(appId));
            //异步任务拉取模板信息
            threadPool.execute(() -> syncBiz.syncTemplate(appId));
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            return RpcResultBuilder.fail(500, e.getMessage());
        }
        return RpcResultBuilder.succ(BaseResult.class);
    }

    @Override
    public WxSystemConfigResult getComponentToken() {
        Date current = new Date(System.currentTimeMillis());
        WxSystemConfigResult result = wxSystemCongfigService.findConfigByCode(WxSystemConfigKeyEnum.component_access_token.getCode());
        WxSystemConfigDTO tokenInfo = result.getValue();

        if (tokenExpire(current, tokenInfo.getGmtModified()) || null == tokenInfo) {
            return RpcResultBuilder.fail(WxSystemConfigResult.class, Errors.WxSystemConfigError.COMPONENT_ACCESS_TOKEN_EXPIRED);
        }
        WxSystemConfigResult wxSystemConfigResult = RpcResultBuilder.succ(WxSystemConfigResult.class);
        wxSystemConfigResult.setValue(BeanConv.copy(tokenInfo, WxSystemConfigDTO.class));

        LOGGER.info("getComponentToken: get getComponentToken! tokenInfo={}", tokenInfo);
        return wxSystemConfigResult;
    }

    /**
     * 判断token是否过期
     *
     * @param current
     * @param compare
     * @return
     */
    private boolean tokenExpire(Date current, Date compare) {
        return ((current.getTime() - compare.getTime()) >= 2 * 60 * 60 * 1000L);
    }
}
